package com.zxjbyte.yiyi.extension.job.core.processor;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.lang.Singleton;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.zxjbyte.yiyi.extension.job.config.YiyiQuartzProperties;
import com.zxjbyte.yiyi.extension.job.domain.dto.JobLogAddDTO;
import com.zxjbyte.yiyi.extension.job.enums.JobDataKeyEnum;
import com.zxjbyte.yiyi.extension.job.service.JobLogService;
import com.zxjbyte.yiyi.framework.common.constant.Constant;
import com.zxjbyte.yiyi.framework.common.constant.StatusConstant;
import com.zxjbyte.yiyi.framework.common.util.ClassXUtils;
import com.zxjbyte.yiyi.framework.common.util.JsonUtil;
import com.zxjbyte.yiyi.extension.job.core.alarm.AlarmPayload;
import com.zxjbyte.yiyi.extension.job.core.alarm.EmailAlarmSender;
import com.zxjbyte.yiyi.extension.job.core.alarm.JobAlarmContext;
import com.zxjbyte.yiyi.extension.job.enums.JobAlarmTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.scheduling.quartz.QuartzJobBean;

import javax.annotation.Resource;
import java.util.Date;
import java.util.Objects;

/**
 * 基础任务调通
 *
 * @Author zhangxingjia
 * @Date 2023/9/6 17:55
 * @Version: 1.0
 */
@Slf4j
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class BaseInvokingJob extends QuartzJobBean {

    @Resource
    private YiyiQuartzProperties properties;
    @Resource
    private JobLogService jobLogService;

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        JobDataMap mergedJobDataMap = context.getMergedJobDataMap();
        String jobClassName = mergedJobDataMap.getString(JobDataKeyEnum.JOB_CLASS_NAME.name());
        if(StrUtil.isBlank(jobClassName)){
            log.warn("Quartz Job execute fail >>>> job class name is empty.");
            return;
        }
        boolean needRecordLog = mergedJobDataMap.getBoolean(JobDataKeyEnum.NEED_RECORD_LOG.name());
        Object methodParam = mergedJobDataMap.get(JobDataKeyEnum.METHOD_PARAM.name());
        // 失败重试次数
        int failRetryCount = (int) mergedJobDataMap.getOrDefault(JobDataKeyEnum.FAIL_RETRY_COUNT.name(), 0);
        // 重试间隔毫秒
        long retryIntervalInMillis = (long) mergedJobDataMap.getOrDefault(JobDataKeyEnum.RETRY_INTERVAL_IN_MILLIS.name(), 0L);
        // 报警邮件
        String alarmParam = mergedJobDataMap.getString(JobDataKeyEnum.EMAIL_ALARM.name());
        // 任务被重新触发的次数
        int refireCount = context.getRefireCount();
        // 触发器实际触发时间
        Date fireTime = context.getFireTime();
        JobKey jobKey = context.getJobDetail().getKey();
        Date beginTime = null;
        Date endTime = null;
        int isSuccess = StatusConstant.SUCCESS;
        try {
            Class<?> clazz = ClassXUtils.forName(jobClassName, ClassXUtils.getDefaultClassLoader());
            if(ClassXUtils.isAssignable(JobHandler.class, clazz)){
                // 实现JobHandler接口的类需要添加 @Component注解定义成bean加入spring容器
                JobHandler jobHandler = (JobHandler) SpringUtil.getBean(clazz);
                beginTime = new Date();
                jobHandler.execute(methodParam);
            } else {
                String jobMethodName = mergedJobDataMap.getString(JobDataKeyEnum.JOB_METHOD_NAME.name());
                if(StrUtil.isBlank(jobMethodName)){
                    log.warn("Quartz Job execute fail >>>> job method name is empty.");
                    return;
                }
                // 未实现JobHandler接口的类，需要有一个无参的构造函数来实例化对象
                beginTime = new Date();
                if(ObjectUtil.isEmpty(methodParam)){
                    ReflectUtil.invoke(Singleton.get(clazz), jobMethodName);
                } else {
                    ReflectUtil.invoke(Singleton.get(clazz), jobMethodName, methodParam);
                }
            }
            endTime = new Date();
            long diff = endTime.getTime() - beginTime.getTime();
            sendAlertEmail(alarmParam, jobKey.toString(), JobAlarmTypeEnum.TIMEOUT, null, diff);
            log.info("Quartz job execute success >>>> [jobClassName: {}], [threadName: {}], [fireTime: {}], [taskExecutionDuration: {} ms]", jobClassName,
                    Thread.currentThread().getName(), DateUtil.format(fireTime, Constant.PATTERN_DATETIME_MS), diff);
        } catch (Throwable ex) {
            log.error(ex.getMessage(), ex);
            isSuccess = StatusConstant.FAIL;
            sendAlertEmail(alarmParam, jobKey.toString(), JobAlarmTypeEnum.EXCEPTION, ex, null);
            // 达到重试上线直接抛异常
            if(refireCount >= failRetryCount){
                throw new JobExecutionException(ex);
            }
            ThreadUtil.sleep(retryIntervalInMillis);
            log.info("Quartz job execute exception >>>> jobKey：{}，refireCount：{}",jobKey.toString(), refireCount);
            throw new JobExecutionException(ex, true);
        } finally {
            if(needRecordLog){
                addJobLog(jobKey, context.getTrigger().getKey(), fireTime, beginTime, endTime, refireCount, isSuccess);
            }
        }
    }

    /**
     * 发送报警的邮件
     * @param jobAlarmType
     * @param ex
     * @param diff
     */
    private void sendAlertEmail(String alarmParam, String jobKey, JobAlarmTypeEnum jobAlarmType, Throwable ex, Long diff){
        if(StrUtil.isEmpty(alarmParam)){
            return;
        }
        AlarmPayload alarmPayloadObj = JsonUtil.parseObject(alarmParam, AlarmPayload.class);
        if(!Objects.equals(alarmPayloadObj.getAlarmType(), jobAlarmType)){
            return;
        }
        if(ArrayUtil.isEmpty(alarmPayloadObj.getEmailAddress()) && StrUtil.isEmpty(properties.getJobAlarm().getToEmailAddress())){
            return;
        }
        long timeout = alarmPayloadObj.getTimeout() == null ?  properties.getJobAlarm().getTimeout().toMillis() : alarmPayloadObj.getTimeout();
        if(Objects.equals(jobAlarmType, JobAlarmTypeEnum.TIMEOUT) && diff <= timeout){
            return;
        }
        JobAlarmContext context = new JobAlarmContext();
        context.setExpression(alarmPayloadObj.getCronExpression());
        context.setTypeEnum(jobAlarmType);
        context.setJobKey(jobKey);
        context.setAlarmTime(new Date());
        context.setJobId(alarmPayloadObj.getJobId());
        context.setAlarmEmail(ArrayUtil.isEmpty(alarmPayloadObj.getEmailAddress()) ?
                StrUtil.splitToArray(properties.getJobAlarm().getToEmailAddress(), ",") : alarmPayloadObj.getEmailAddress());
        context.setAuthor(alarmPayloadObj.getAuthor());
        if(Objects.equals(jobAlarmType, JobAlarmTypeEnum.EXCEPTION)){
            context.setErrMsg(ExceptionUtil.stacktraceToString(ex, Integer.MAX_VALUE));
        } else {
            context.setErrMsg("任务调度中心执行任务超时，超时时间为：" + diff + " ms，告警阀值时间为：" + timeout + " ms。");
        }
        ThreadUtil.execute(() -> EmailAlarmSender.getInstance().doAlarm(context));
    }

    /**
     * 记录调度日志
     */
    private void addJobLog(JobKey jobKey, TriggerKey triggerKey, Date fireTime,
                        Date beginTime, Date endTime, int refireCount, Integer status){
        JobLogAddDTO dto = new JobLogAddDTO();
        dto.setJobKey(jobKey.toString());
        dto.setTriggerKey(triggerKey.toString());
        dto.setFireTime(fireTime);
        dto.setStartTime(beginTime);
        dto.setEndTime(endTime);
        if(status == StatusConstant.SUCCESS){
            dto.setExecutionTime((int) (endTime.getTime() - beginTime.getTime()));
        }
        dto.setRefireCount(refireCount);
        dto.setStatus(status);
        jobLogService.asyncAddLog(dto);
    }
}
